﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Objects.DataClasses;
using System.Linq;
using CivilRegistry.ControlledModel.Repository.Extensions;
using CivilRegistry.Logger;

namespace CivilRegistry.ControlledModel.Repository
{
    /// <summary>
    /// The abstract repository for civil registry model.
    /// </summary>
    /// <typeparam name="TEntity">The entity.</typeparam>
    public abstract class AbstractRepository<TEntity> : IabstractRepository<TEntity>
        where TEntity : EntityObject
    {
        #region LocalFields

        /// <summary>
        /// To hold the context instance.
        /// </summary>
        protected CivilRegistryEntities civilContext;

        #endregion

        #region Constructores

        /// <summary>
        /// Constructs the AbstractRepository class.
        /// </summary>
        public AbstractRepository()
        {
            civilContext = CivilRegistryEntities.Instance;
        }

        #endregion

        #region Properties

        #endregion

        #region Methods

        #region FinderMethods

        /// <summary>
        /// Finds the entity object by passed predicate.
        /// </summary>
        /// <param name="predicate">The predicate.</param>
        /// <returns>Found entity object, null if not found.</returns>
        public virtual TEntity FindBy(Func<TEntity, bool> predicate)
        {
            TEntity entity = civilContext.CreateObjectSet<TEntity>().FirstOrDefault(predicate);

            if (entity != null)
            {
                string entityName = entity.ToString().Replace("CivilRegistry.ControlledModel.", "");

                // Check whether the entity is a user.
                if (entityName == "Person" || entityName == "Admin" || entityName == "SuperAdmin" || entityName == "Hospital")
                {
                    entityName = "User";
                }

                if (Mediator.EntitiesToRefresh.Contains(entityName))
                {
                    this.Refresh(entity);
                    Mediator.EntitiesToRefresh.Remove(entityName);
                }
            }

            return entity;
        }

        /// <summary>
        /// Finds all entity objects of the type.
        /// </summary>
        /// <returns>Found entity objects, empty if none are found.</returns>
        public virtual List<TEntity> FindAll()
        {
            return this.FindAll(predicate => true);
        }

        /// <summary>
        /// Finds all entity objects of the type.
        /// </summary>
        /// <param name="predicate">The predicate to filter.</param>
        /// <returns>Found entity objects, empty if none are found.</returns>
        public virtual List<TEntity> FindAll(Func<TEntity, bool> predicate)
        {
            List<TEntity> entities = civilContext.CreateObjectSet<TEntity>().Where(predicate).ToList<TEntity>();

            foreach (var entity in entities)
            {
                if (entity != null)
                {
                    string entityName = entity.ToString().Replace("CivilRegistry.ControlledModel.", "");

                    if (entityName == "Person" || entityName == "Admin" || entityName == "SuperAdmin" || entityName == "Hospital")
                    {
                        entityName = "User";
                    }

                    if (Mediator.EntitiesToRefresh.Contains(entityName))
                    {
                        this.Refresh(entity);
                        Mediator.EntitiesToRefresh.Remove(entityName);
                    }
                }
            }

            return entities;
        }

        #endregion

        /// <summary>
        /// To update the entity on database.
        /// </summary>
        /// <param name="entity">The entity to update.</param>
        /// <returns>The updated entity object.</returns>
        public TEntity Update(TEntity entity)
        {
            try
            {
                civilContext.SaveChanges();
            }
            catch (Exception ex)
            {
                ErrorLogger.Log(ex);

                try
                {
                    this.Refresh(entity);
                    civilContext.SaveChanges();
                    civilContext.Detach(entity);
                }
                catch
                {
                    //Ignore this.
                }

                throw ex;
            }
            return entity;
        }

        /// <summary>
        /// To insert the entity object to databse.
        /// </summary>
        /// <param name="entity">The entity object to insert.</param>
        /// <returns>The inserted entity object.</returns>
        public TEntity Insert(TEntity entity)
        {
            try
            {
                civilContext.AddObject(civilContext.GetEntitySetName(entity), entity);

                if (entity.EntityState == EntityState.Detached)
                    civilContext.Attach(entity);

                civilContext.SaveChanges();

                civilContext.Detach(entity);
            }
            catch (Exception ex)
            {
                ErrorLogger.Log(ex);

                try
                {
                    this.Refresh(entity);
                    civilContext.SaveChanges();
                    civilContext.Detach(entity);
                }
                catch
                {
                    //Ignore this.
                }

                throw ex;
            }

            return entity;
        }

        /// <summary>
        /// To delete the entity object from database.
        /// </summary>
        /// <param name="entity">The entity object to delete.</param>
        public virtual void Delete(TEntity entity)
        {
            try
            {
                civilContext.DeleteObject(entity);
                civilContext.SaveChanges();
            }
            catch (Exception ex)
            {
                ErrorLogger.Log(ex);

                try
                {
                    this.Refresh(entity);
                    civilContext.SaveChanges();
                    civilContext.Detach(entity);
                }
                catch
                {
                    //Ignore this.
                }

                throw ex;
            }
        }

        /// <summary>
        /// To refresh the entity from database.
        /// </summary>
        /// <param name="entity">The entity to refresh.</param>
        public void Refresh(TEntity entity)
        {
            try
            {
                civilContext.Refresh(System.Data.Objects.RefreshMode.StoreWins, entity);
            }
            catch (Exception ex)
            {
                ErrorLogger.Log(ex);
                throw ex;
            }
        }

        /// <summary>
        /// To refresh all the entities from database.
        /// </summary>
        public void RefreshAll()
        {
            try
            {
                civilContext.Refresh(System.Data.Objects.RefreshMode.StoreWins, civilContext.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted));
                civilContext.Refresh(System.Data.Objects.RefreshMode.StoreWins, civilContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added));
                civilContext.Refresh(System.Data.Objects.RefreshMode.StoreWins, civilContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified));
            }
            catch (Exception ex)
            {
                ErrorLogger.Log(ex);
                throw ex;
            }
        }

        #endregion
    }
}